package com.example.auth.service.impl;

import com.example.auth.dto.ProjectDTO;
import com.example.auth.dto.TaskSubmissionDTO;
import com.example.auth.entity.Task;
import com.example.auth.entity.TaskSubmission;
import com.example.auth.entity.User;
import com.example.auth.repository.TaskRepository;
import com.example.auth.repository.TaskSubmissionRepository;
import com.example.auth.repository.UserRepository;
import com.example.auth.service.ProjectService;
import com.example.auth.service.TaskSubmissionService;
import com.example.auth.config.FileStorageConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class TaskSubmissionServiceImpl implements TaskSubmissionService {

    private static final Logger logger = LoggerFactory.getLogger(TaskSubmissionServiceImpl.class);

    @Value("${app.upload.dir:uploads}")
    private String uploadDir;

    @Autowired
    private TaskSubmissionRepository taskSubmissionRepository;

    @Autowired
    private TaskRepository taskRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ProjectService projectService;
    
    @Autowired
    private FileStorageConfig fileStorageConfig;

    @Override
    @Transactional
    public TaskSubmissionDTO submitTaskFile(Long taskId, MultipartFile file, Long submitterId, String comment) {
        logger.info("Submitting task file, Task ID: {}, Submitter ID: {}", taskId, submitterId);

        try {
            // Verify if the task exists
            Optional<Task> taskOpt = taskRepository.findById(taskId);
            if (!taskOpt.isPresent()) {
                logger.error("Task not found, Task ID: {}", taskId);
                throw new RuntimeException("Task not found");
            }

            Task task = taskOpt.get();
            logger.info("Verifying task submission permissions, Task ID: {}, Task assigned to User ID: {}, Task Supervisor ID: {}, Task Completion Status: {}, Submitter ID: {}",
                    taskId, task.getAssigneeId(), task.getSupervisorId(), task.getCompleted(), submitterId);

            // Check if task is associated with a project in Review Rejected status
            if (task.getProjectId() != null) {
                ProjectDTO project = projectService.getProjectById(task.getProjectId());
                if ("Review Rejected".equals(project.getStatus())) {
                    logger.warn("Cannot submit file for task in Review Rejected project, Project ID: {}, Task ID: {}", task.getProjectId(), taskId);
                    throw new RuntimeException("Cannot submit file for task in Review Rejected project");
                }
            }

            // Verify submitter permissions
            // Allow three cases:
            // 1. Submitter is the task assignee (regardless of task status)
            // 2. assigneeId is null and submitter is the task supervisor
            // 3. Submitter is the task supervisor
            boolean hasAccess = false;

            if (task.getAssigneeId() != null && submitterId.equals(task.getAssigneeId())) {
                // Submitter is the task assignee (even if the task is completed, uploading is allowed)
                hasAccess = true;
                logger.info("Submitter is the assigned student for the task, submission allowed, Submitter ID: {}, Task ID: {}, Task Completed: {}",
                        submitterId, taskId, task.getCompleted());
            } else if (task.getAssigneeId() == null && submitterId.equals(task.getSupervisorId())) {
                // assigneeId is null and submitter is the task supervisor
                hasAccess = true;
                logger.info("Task is not assigned to any student, but submitter is the task supervisor, submission allowed, Submitter ID: {}, Task ID: {}", submitterId, taskId);
            } else if (submitterId.equals(task.getSupervisorId())) {
                // Submitter is the task supervisor
                hasAccess = true;
                logger.info("Submitter is the task supervisor, submission allowed, Submitter ID: {}, Task ID: {}", submitterId, taskId);
            }

            if (!hasAccess) {
                logger.error("Failed to submit task file: Unauthorized to submit this task, Task ID: {}, Task assigned to User ID: {}, Task Supervisor ID: {}, Submitter ID: {}, Submitter and assigneeId match: {}, Submitter and supervisorId match: {}",
                        taskId, task.getAssigneeId(), task.getSupervisorId(), submitterId,
                        submitterId.equals(task.getAssigneeId()), submitterId.equals(task.getSupervisorId()));
                throw new RuntimeException("Unauthorized to submit this task");
            }

            // Use FileStorageConfig to ensure the task directory exists
            Path taskUploadPath = fileStorageConfig.ensureTaskDirectory(taskId);
            logger.info("Task upload directory: {}", taskUploadPath.toString());

            // Get file information
            String originalFilename = file.getOriginalFilename();
            // Safely process filename to avoid path traversal attacks
            if (originalFilename != null) {
                originalFilename = new File(originalFilename).getName();
            } else {
                originalFilename = "unknown_file";
            }
            
            String fileExtension = "";
            if (originalFilename.contains(".")) {
                fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
            }
            
            String fileType = file.getContentType();

            // Check and truncate the file type, to prevent database field overflow
            if (fileType != null && fileType.length() > 255) {
                logger.warn("File type string is too long, will be truncated: {}", fileType);
                fileType = fileType.substring(0, 255);
            }

            long fileSize = file.getSize();

            // Generate a unique filename
            String filename = UUID.randomUUID().toString() + fileExtension;
            Path filePath = taskUploadPath.resolve(filename);

            // Save the file
            Files.copy(file.getInputStream(), filePath);
            logger.info("File saved successfully: {}", filePath.toString());

            // Get relative path (starting from uploads)
            String relativeFilePath = "task_submissions/task_" + taskId + "/" + filename;
            
            // Create submission record, using the task-specific path
            TaskSubmission submission = new TaskSubmission();
            submission.setTaskId(taskId);
            submission.setFilePath("/uploads/" + relativeFilePath);
            submission.setOriginalFilename(originalFilename);
            submission.setFileType(fileType);
            submission.setFileSize(fileSize);
            submission.setSubmitterId(submitterId);
            submission.setComment(comment);

            // Save the submission record
            TaskSubmission savedSubmission = taskSubmissionRepository.save(submission);

            // Update task status: change to waiting for review status after file submission
            logger.info("Updating task status to waiting for review, Task ID: {}", taskId);
            task.setCompleted(false); // Mark task as incomplete since it needs teacher review to be considered complete
            task.setReviewNeeded(true); // Set review needed flag to notify the teacher
            task.setStatus("Waiting for Review"); // Update task status to waiting for review after submission
            taskRepository.save(task);

            // Update project status
            if (task.getProjectId() != null) {
                projectService.updateProjectStatusBasedOnTasks(task.getProjectId());
            }

            // Get the task title and submitter name
            String taskTitle = task.getTitle();
            String submitterName = getUserNameById(submitterId);

            logger.info("Task file submitted successfully, Task ID: {}, Submitter ID: {}, Submission ID: {}",
                    taskId, submitterId, savedSubmission.getId());

            return TaskSubmissionDTO.fromEntity(savedSubmission, taskTitle, submitterName);
        } catch (IOException e) {
            logger.error("File upload failed, Task ID: {}, Submitter ID: {}, Error: {}", taskId, submitterId, e.getMessage(), e);
            throw new RuntimeException("File upload failed: " + e.getMessage());
        } catch (Exception e) {
            logger.error("Failed to submit task file, Task ID: {}, Submitter ID: {}, Error: {}", taskId, submitterId, e.getMessage(), e);
            throw new RuntimeException("Failed to submit task file: " + e.getMessage());
        }
    }

    @Override
    public List<TaskSubmissionDTO> getSubmissionsByTaskId(Long taskId, Long userId) {
        logger.info("Getting task submission records, Task ID: {}, User ID: {}", taskId, userId);

        try {
            // Get task details
            Optional<Task> taskOpt = taskRepository.findById(taskId);
            if (!taskOpt.isPresent()) {
                logger.error("Task not found, Task ID: {}", taskId);
                throw new RuntimeException("Task not found");
            }

            Task task = taskOpt.get();
            String taskTitle = task.getTitle();

            // Permission check
            boolean isSupervisor = userId.equals(task.getSupervisorId());
            boolean isAssignee = userId.equals(task.getAssigneeId());
            
            // Get project information, check if user is project supervisor
            boolean isProjectSupervisor = false;
            if (task.getProjectId() != null) {
                // Get project information and check if current user is project supervisor
                try {
                    isProjectSupervisor = projectService.isUserProjectSupervisor(task.getProjectId(), userId);
                    logger.info("Checking if user is project supervisor: User ID: {}, Project ID: {}, Result: {}",
                            userId, task.getProjectId(), isProjectSupervisor);
                } catch (Exception e) {
                    logger.warn("Failed to check project supervisor status: {}", e.getMessage());
                }
            }

            logger.info("Checking file access permissions: User ID: {}, Is Supervisor: {}, Is Project Supervisor: {}, Is Assignee for this task: {}",
                    userId, isSupervisor, isProjectSupervisor, isAssignee);

            if (!isSupervisor && !isAssignee && !isProjectSupervisor) {
                // User is neither task supervisor, nor assigned student, nor project supervisor, return empty list
                logger.warn("User is not authorized to view the submission records for this task, User ID: {}, Task ID: {}", userId, taskId);
                return new ArrayList<>();
            }

            // Get submission records
            List<TaskSubmission> submissions = taskSubmissionRepository.findByTaskId(taskId);

            return submissions.stream()
                    .map(submission -> {
                        String submitterName = getUserNameById(submission.getSubmitterId());
                        return TaskSubmissionDTO.fromEntity(submission, taskTitle, submitterName);
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get task submission records", e);
            throw new RuntimeException("Failed to get task submission records: " + e.getMessage());
        }
    }

    @Override
    public List<TaskSubmissionDTO> getSubmissionsBySubmitterId(Long submitterId) {
        logger.info("Getting user submission records, Submitter ID: {}", submitterId);

        try {
            List<TaskSubmission> submissions = taskSubmissionRepository.findBySubmitterId(submitterId);

            return submissions.stream()
                    .map(submission -> {
                        // Get task title
                        Optional<Task> taskOpt = taskRepository.findById(submission.getTaskId());
                        String taskTitle = taskOpt.isPresent() ? taskOpt.get().getTitle() : "Unknown Task";

                        String submitterName = getUserNameById(submitterId);
                        return TaskSubmissionDTO.fromEntity(submission, taskTitle, submitterName);
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get user submission records", e);
            throw new RuntimeException("Failed to get user submission records: " + e.getMessage());
        }
    }

    @Override
    public TaskSubmissionDTO getSubmissionById(Long submissionId) {
        logger.info("Getting submission details, Submission ID: {}", submissionId);

        try {
            Optional<TaskSubmission> submissionOpt = taskSubmissionRepository.findById(submissionId);

            if (submissionOpt.isPresent()) {
                TaskSubmission submission = submissionOpt.get();

                // Get task title
                Optional<Task> taskOpt = taskRepository.findById(submission.getTaskId());
                String taskTitle = taskOpt.isPresent() ? taskOpt.get().getTitle() : "Unknown Task";

                String submitterName = getUserNameById(submission.getSubmitterId());

                return TaskSubmissionDTO.fromEntity(submission, taskTitle, submitterName);
            } else {
                throw new RuntimeException("Submission record not found");
            }
        } catch (Exception e) {
            logger.error("Failed to get submission details", e);
            throw new RuntimeException("Failed to get submission details: " + e.getMessage());
        }
    }

    @Override
    @Transactional
    public boolean deleteSubmission(Long submissionId) {
        logger.info("Deleting submission record, Submission ID: {}", submissionId);

        try {
            Optional<TaskSubmission> submissionOpt = taskSubmissionRepository.findById(submissionId);

            if (submissionOpt.isPresent()) {
                TaskSubmission submission = submissionOpt.get();
                Long taskId = submission.getTaskId();

                // Delete file
                String filePath = submission.getFilePath();
                if (filePath != null && !filePath.isEmpty()) {
                    // Remove the "/uploads/" prefix from the path
                    String relativePath = filePath.replaceFirst("^/uploads/", "");
                    Path path = Paths.get(uploadDir, relativePath);
                    try {
                        Files.deleteIfExists(path);
                        logger.info("File deleted successfully: {}", path);
                    } catch (IOException e) {
                        logger.warn("Failed to delete file: {}", path, e);
                    }
                }

                // Get task information
                Optional<Task> taskOpt = taskRepository.findById(taskId);

                // Delete submission record
                taskSubmissionRepository.deleteById(submissionId);
                logger.info("Submission record deleted successfully: SubmissionID={}, TaskID={}", submissionId, taskId);

                // Get the current number of submission records for the task
                long submissionCount = taskSubmissionRepository.countByTaskId(taskId);

                // Update task status
                if (taskOpt.isPresent()) {
                    Task task = taskOpt.get();
                    boolean hasSubmissions = submissionCount > 0;

                    // Always update status, ensure it's set correctly based on submission status
                    if (!hasSubmissions) {
                        // If there are no submissions, reset status and cancel review flag
                        task.setCompleted(false);
                        task.setReviewNeeded(false);
                        // Reset task status to "In Progress" when no submissions exist
                        task.setStatus("In Progress");
                    } else {
                        // Still has submissions, maintain waiting for review status
                        task.setReviewNeeded(true);
                        task.setStatus("Waiting for Review");
                    }
                    
                    taskRepository.save(task);
                    logger.info("Task status updated after file deletion, Completed: {}, Review Needed: {}, Status: {}, Task ID: {}", 
                        task.getCompleted(), task.getReviewNeeded(), task.getStatus(), taskId);

                    // Update project status
                    if (task.getProjectId() != null) {
                        projectService.updateProjectStatusBasedOnTasks(task.getProjectId());
                        logger.info("Project status updated, Project ID: {}", task.getProjectId());
                    }
                }

                return true;
            } else {
                logger.warn("Submission record not found, Submission ID: {}", submissionId);
                return false;
            }
        } catch (Exception e) {
            logger.error("Failed to delete submission record", e);
            throw new RuntimeException("Failed to delete submission record: " + e.getMessage());
        }
    }

    @Override
    @Transactional
    public TaskSubmissionDTO updateSubmissionComment(Long submissionId, String comment) {
        logger.info("Updating submission comment, Submission ID: {}", submissionId);

        try {
            Optional<TaskSubmission> submissionOpt = taskSubmissionRepository.findById(submissionId);

            if (submissionOpt.isPresent()) {
                TaskSubmission submission = submissionOpt.get();

                // Only update the comment, do not affect other fields
                submission.setComment(comment);

                TaskSubmission updatedSubmission = taskSubmissionRepository.save(submission);
                logger.info("Submission comment updated successfully, Submission ID: {}", submissionId);

                // Get task title
                Optional<Task> taskOpt = taskRepository.findById(submission.getTaskId());
                String taskTitle = taskOpt.isPresent() ? taskOpt.get().getTitle() : "Unknown Task";

                String submitterName = getUserNameById(submission.getSubmitterId());

                return TaskSubmissionDTO.fromEntity(updatedSubmission, taskTitle, submitterName);
            } else {
                logger.warn("Submission record not found, Submission ID: {}", submissionId);
                throw new RuntimeException("Submission record not found");
            }
        } catch (Exception e) {
            logger.error("Failed to update submission comment", e);
            throw new RuntimeException("Failed to update submission comment: " + e.getMessage());
        }
    }

    @Override
    public boolean isTaskSubmitted(Long taskId) {
        return taskSubmissionRepository.findByTaskId(taskId).size() > 0;
    }

    @Override
    public Resource loadFileAsResource(String filePath) {
        try {
            logger.info("Attempting to load file resource: {}", filePath);
            
            // Ensure file path matches expected format
            String originalPath = filePath;
            
            // Remove /uploads/ prefix from the path
            String relativePath = filePath.replaceFirst("^/uploads/", "");
            relativePath = relativePath.replaceFirst("^uploads/", ""); // Ensure no uploads prefix
            
            // Build complete file path
            Path fullPath = Paths.get(uploadDir, relativePath).toAbsolutePath().normalize();
            logger.info("Built complete file path: {}", fullPath);
            
            // Verify file exists
            File file = fullPath.toFile();
            if (!file.exists()) {
                logger.error("File does not exist, path: {}", fullPath);
                
                // Try to get file name directly and search in various possible directories
                String fileName = new File(relativePath).getName();
                logger.info("Attempting to find file in various directories: {}", fileName);
                
                // Search in base upload directory
                File uploadBaseDir = new File(uploadDir);
                if (uploadBaseDir.exists() && uploadBaseDir.isDirectory()) {
                    // 1. Look directly in uploads directory
                    File possibleFile = new File(uploadBaseDir, fileName);
                    if (possibleFile.exists()) {
                        logger.info("Found file in uploads root directory: {}", possibleFile.getAbsolutePath());
                        return new UrlResource(possibleFile.toURI());
                    }
                    
                    // 2. Look in task_submissions directory
                    File taskSubmissionsDir = new File(uploadBaseDir, "task_submissions");
                    if (taskSubmissionsDir.exists() && taskSubmissionsDir.isDirectory()) {
                        possibleFile = new File(taskSubmissionsDir, fileName);
                        if (possibleFile.exists()) {
                            logger.info("Found file in task_submissions directory: {}", possibleFile.getAbsolutePath());
                            return new UrlResource(possibleFile.toURI());
                        }
                        
                        // 3. Look in all task_X subdirectories
                        File[] taskDirs = taskSubmissionsDir.listFiles(f -> f.isDirectory() && f.getName().startsWith("task_"));
                        if (taskDirs != null) {
                            for (File taskDir : taskDirs) {
                                possibleFile = new File(taskDir, fileName);
                                if (possibleFile.exists()) {
                                    logger.info("Found file in {} directory: {}", taskDir.getName(), possibleFile.getAbsolutePath());
                                    return new UrlResource(possibleFile.toURI());
                                }
                            }
                        }
                    }
                }
                
                // Extract task ID (if available)
                if (relativePath.contains("task_") && relativePath.contains("/")) {
                    String[] parts = relativePath.split("/");
                    for (String part : parts) {
                        if (part.startsWith("task_")) {
                            String taskId = part.replace("task_", "");
                            logger.info("Extracted taskId from path: {}", taskId);
                            
                            // Get all submission records for this task
                            try {
                                Long taskIdLong = Long.parseLong(taskId);
                                List<TaskSubmission> submissions = taskSubmissionRepository.findByTaskId(taskIdLong);
                                for (TaskSubmission submission : submissions) {
                                    // Try to get correct file path from database record
                                    if (submission.getOriginalFilename().equals(fileName) || 
                                        submission.getFilePath().endsWith(fileName)) {
                                        String correctedPath = submission.getFilePath().replaceFirst("^/uploads/", "");
                                        Path correctedFullPath = Paths.get(uploadDir, correctedPath);
                                        if (Files.exists(correctedFullPath)) {
                                            logger.info("Found correct file path from database record: {}", correctedFullPath);
                                            return new UrlResource(correctedFullPath.toUri());
                                        }
                                    }
                                }
                            } catch (NumberFormatException | NullPointerException e) {
                                logger.warn("Unable to parse taskId or query submission records: {}", e.getMessage());
                            }
                            break;
                        }
                    }
                }
                
                throw new RuntimeException("File does not exist: " + originalPath);
            }
            
            Resource resource = new UrlResource(fullPath.toUri());
            if (resource.exists()) {
                logger.info("File resource loaded successfully: {}", fullPath);
                return resource;
            } else {
                logger.error("Failed to load file resource: {}", fullPath);
                throw new RuntimeException("File does not exist or cannot be read: " + originalPath);
            }
        } catch (MalformedURLException e) {
            logger.error("URL format error: {}, Error: {}", filePath, e.getMessage());
            throw new RuntimeException("URL format error: " + filePath, e);
        } catch (Exception e) {
            logger.error("Unknown error loading file resource: {}, Error: {}", filePath, e.getMessage());
            throw new RuntimeException("Unknown error loading file resource: " + filePath, e);
        }
    }

    /**
     * Gets the username by user ID
     */
    private String getUserNameById(Long userId) {
        if (userId == null) {
            return null;
        }

        try {
            Optional<User> userOpt = userRepository.findById(userId);
            if (userOpt.isPresent()) {
                User user = userOpt.get();
                return user.getRealName() != null ? user.getRealName() : user.getUsername();
            }
        } catch (Exception e) {
            logger.warn("Failed to get username, ID: {}", userId, e);
        }

        return "Unknown User";
    }
}